<?php
/**
 *  Ueditor.php Created by : PhpStorm
 *
 * @author: 魏巍 <524314430@qq.com>
 * @date  : 2021/9/16 17:31
 */

namespace app\admin\controller;

use app\admin\model\SystemUploadfile;
use app\common\controller\AdminController;
use EasyAdmin\upload\Uploadfile;
use think\App;
use think\file\UploadedFile;
use think\Request;

class Ueditor extends AdminController
{
    private $config_path;
    private $uediort_config;

    public function __construct(App $app)
    {
        parent::__construct($app);
        $this->config_path = public_path('static/plugs/ueditor-1.5.0') . 'config.json';
        $this->uediort_config = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents($this->config_path)), true);
    }

    /**
     * @param string $action
     * @param string $file
     * @param int    $start
     * @param int    $size
     *
     * @return array|\think\response\Json
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function index($action = '', $file = "upfile", $start=0, $size=20)
    {
        $start = intval($start);
        switch ($action) {
            case 'listfile':
                // doc,docx,gif,ico,icon,jpg,mp3,mp4,p12,pem,png,rar,jpeg,pdf,xls,xlsx
                $model = SystemUploadfile:: field('id,original_name,url,file_size,file_ext')
                    ->whereIn('file_ext', 'mp3,mp4,doc,docx,rar,pdf,xls,xlsx');
                $arr['state'] = 'SUCCESS';
                $arr['list'] = $model->limit($start, $size)->select();
                $arr['start'] = $start;
                $arr['total'] = $model->count('id');
                $result = $arr;
                break;
            case 'listimage':
                $model = SystemUploadfile:: field('id,original_name,url,file_size,file_ext')
                    ->whereIn('file_ext', 'gif,ico,icon,jpg,png,jpeg');
                $arr['state'] = 'SUCCESS';
                $arr['list'] = $model->limit($start, $size)->select();
                $arr['start'] = $start;
                $arr['total'] = $model->count('id');
                $result = $arr;
                break;
            case 'uploadfile':
            case 'uploadvideo':
            case 'uploadimage':
                $r = $this->_upload($this->request, $file);
                if ($r && $r['status']) {
                    $arr['url'] = $r['url'];
                    $arr['title'] = $r['original_name'];
                    $arr['state'] = 'SUCCESS';
                    $result = $arr;
                } else {
                    $arr['state'] = 'ERROR';
                    $result = $arr;
                }
                break;
            case 'catchimage':
                $source = $_POST['source'];
                $list = [];
                foreach ($source as $v) {
                    $info = $this->saveRemoteImage($v);
                    array_push($list, array(
                        "state" => $info["state"],
                        "url" => $info["url"],
                        "size" => $info["size"],
                        "title" => htmlspecialchars($info["title"]),
                        "original" => htmlspecialchars($info["original"]),
                        "source" => htmlspecialchars($v)
                    ));
                }
                $arr['state'] = count($list) ? 'SUCCESS' : 'ERROR';
                $arr['list'] = $list;
                unset($arr['original']);
                unset($arr['type']);
                unset($arr['size']);
                $result = $arr;
                break;
            case 'config':
                $result = $this->uediort_config;
                break;
            default:
                $arr['state'] = '请求地址出错';
                $result = $arr;
                break;
        }
        /* 输出结果 */
        if (isset($_GET["callback"])) {
            if (preg_match("/^[\w_]+$/", $_GET["callback"])) {
                echo htmlspecialchars($_GET["callback"]) . '(' . json_encode($result) . ')';
            } else {
                return ['state' => 'callback参数不合法'];
            }
        } else {
            return json($result);
        }
    }

    /**
     * @param string $finder
     *
     * @return string
     */
    private function setPicName($finder = '')
    {
        $name = '';
        $typeData = $this->getTypeFinder($finder);
        if (is_array($typeData)) {
            $t1 = implode('/', $typeData) . '/';
        } else {
            $t1 = $typeData . '/' . date('Ym') . '/';
        }
        $micr = explode(".", microtime(true));
        $name .= $t1 . (date('YmdHis') . rand(0, 9) . $micr[1]);
        return $name;
    }

    /**
     * @param string $finder
     *
     * @return array
     */
    private function getTypeFinder($finder = '')
    {
        $typeData = $finder;
        if (stripos($typeData, '%Ym%') > 0) {
            $typeDatas = str_replace('%Ym%', '/'
                . date('Ym'), $typeData);
            $typeData = explode('/', $typeDatas);
        } else {
            $typeData = [
                $finder,
                date('Ym', time())
            ];
        }
        return $typeData;
    }

    /**
     * 缓存远程图片
     *
     * @param $fileField
     *
     * @return array|bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function saveRemoteImage($fileField)
    {
        $CONFIG = $this->uediort_config;
        $imgUrl = htmlspecialchars($fileField);
        $imgUrl = str_replace("&amp;", "&", $imgUrl);
        //http开头验证
        if (strpos($imgUrl, "http") !== 0) {
            $stateInfo = '链接不是http链接';
            return false;
        }

        preg_match('/(^https*:\/\/[^:\/]+)/', $imgUrl, $matches);
        $host_with_protocol = count($matches) > 1 ? $matches[1] : '';

        // 判断是否是合法 url
        if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) {
            $stateInfo = '非法 URL';
            return false;
        }

        preg_match('/^https*:\/\/(.+)/', $host_with_protocol, $matches);
        $host_without_protocol = count($matches) > 1 ? $matches[1] : '';

        // 此时提取出来的可能是 ip 也有可能是域名，先获取 ip
        $ip = gethostbyname($host_without_protocol);
        // 判断是否是私有 ip
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
            $stateInfo = '非法 IP';
            return false;
        }

        //获取请求头并检测死链
        $heads = get_headers($imgUrl, 1);
        if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) {
            $stateInfo = "链接不可用";
        }
        //格式验证(扩展名验证和Content-Type验证)
        $fileType = strtolower(strrchr($imgUrl, '.'));
        if (strpos($fileType, '?') !== false) {
            $fileType = explode('?', $fileType)[0];
        }
        if (strpos($fileType, '@') !== false) {
            $fileType = explode('@', $fileType)[0];
        }
        if (!in_array($fileType, $CONFIG['catcherAllowFiles']) || !isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")) {
            $stateInfo = '链接contentType不正确';
            return false;
        }
        //打开输出缓冲区并获取远程图片
        ob_start();
        $context = stream_context_create(
            array(
                'http' => array(
                    'follow_location' => false // don't follow redirects
                )
            )
        );
        readfile($imgUrl, false, $context);
        $imgInfo = ob_get_contents();
        ob_end_clean();
        preg_match("/[\/]([^\/]*)[\.]?[^\.\/]*$/", $imgUrl, $m);
        $oriName = $m ? parse_url($m[1])['path'] : "";
        $fileSize = strlen($imgInfo);
        $upload_type = sysconfig('upload', 'upload_type');
        $fileName = $this->setPicName() . $fileType;
        $tmp = explode('/', $fileName);
        $saveDir = runtime_path() . end($tmp);
        @file_put_contents($saveDir, $imgInfo);
        $filed = new UploadedFile($saveDir, $oriName);
        $result = Uploadfile::instance()->setUploadType($upload_type)->setFile($filed)->save();
        if ($result['save']) {
            $files = SystemUploadfile::field('id,original_name,url,file_size,file_ext')
                ->where('url', $result['url'])->find()->toArray();
            return [
                "state" => 'SUCCESS',
                "url" => $files['url'],
                "title" => $files['url'],
                "original" => $files['original_name'],
                "type" => $fileType,
                "size" => $fileSize
            ];
        }else{
            return [
                "state" => 'ERROR',
                "url" => '',
                "title" => $oriName,
                "original" => $oriName,
                "type" => $fileType,
                "size" => $fileSize
            ];
        }
    }

    /**
     * 上传文件
     *
     * @param Request|null $request
     * @param string       $opfile
     *
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function _upload(Request $request = null, $opfile = 'upfile')
    {
        $this->checkPostRequest();
        $data = [
            'upload_type' => $request->post('upload_type') ?? $this->request->param('upload_type'),
            'file' => $request->file($opfile) ?? $this->request->file($opfile),
        ];
        $uploadConfig = sysconfig('upload');
        empty($data['upload_type']) && $data['upload_type'] = $uploadConfig['upload_type'];
        $rule = [
            'upload_type|指定上传类型有误' => "in:{$uploadConfig['upload_allow_type']}",
            'file|文件' => "require|file|fileExt:{$uploadConfig['upload_allow_ext']}|fileSize:{$uploadConfig['upload_allow_size']}",
        ];
        try {
            $this->validate($data, $rule);
            $upload = Uploadfile::instance()
                ->setUploadType($data['upload_type'])
                ->setUploadConfig($uploadConfig)
                ->setFile($data['file'])
                ->save();
        } catch (\Exception $e) {
            return ['url' => '', 'msg' => $e->getMessage(), 'status' => 0];
        }
        if ($upload['save'] == true) {
            $files = SystemUploadfile::field('id,original_name,url,file_size,file_ext')
                ->where('url', $upload['url'])
                ->find()->toArray();
            $files['msg'] = $upload['msg'];
            $files['status'] = 1;
            return $files;
        } else {
            return ['url' => '', 'msg' => $upload['msg'], 'status' => 0];
        }
    }
}